module common.clientsocket;

import std.string, std.concurrency, std.parallelism;

import share.circularbuffer, share.edcode, share.protocol, share.utility, share.nettcp;


alias bool delegate(int length, char[] data) ParsePacket;

public class ClientSocket
{
    // For Receive
    private CircularBuffer!char _c_buffer;
    private char _buffer[8192 * 4];
    private int m_nCurRecv = 0;

    // For Send
    private char _encode_def_msg[32];           // For Send
    private char _encode_body[8192];           
    private char _packet[8192 * 2];
    
    private int _sync;
    
    private DefaultMessage _def_msg;

    private TcpHandler _handler;
    //CWHDefProcess*  m_pxDefProc;
    private ParsePacket _parse_packet;


    
    public this()
    {
        _c_buffer = CircularBuffer!char(256);
        //_buffer = _buffer.init;
        m_nCurRecv = 0;

        _encode_def_msg = _encode_def_msg.init;
        //_encode_body = _encode_body.init;
        //_packet = _packet.init;

        _handler = new TcpHandler;
        _parse_packet = null;
        
        _sync = 1;

        _def_msg = _def_msg.init;
    }

    public void setHandler(ParsePacket parser)
    {
        _parse_packet = parser;
    }

    public bool ConnectToServer(in string addr, ushort port)
    {
        if (_handler.m_nInstanceState != TCP_UNINIT)
            return false;

        if (_handler.Connect(addr, port) == false)
            return false;
        
        _sync = 1;
        
        return true;
    }

    public bool DisconnectToServer()
    {
        if (_handler.m_nInstanceState == TCP_INIT)
        {
            _handler.Disconnect();
            
            return true;
        }

        return false;
    }

    public void poll()
    {
        ushort state;
        PTcpPacket packet;

        //while ( _handler.Poll( state, packet ) ) // use in thread..
        if (_handler.Poll( state, packet ) == true)
        {
            if ( state == TCP_CONNECT )
            {
                std.stdio.writeln("TCP_CONNECT");
            }

            if ( state == TCP_RECV )
            {
                ProcessRecvPacket(packet);
                packet = null;
            }

            if ( state >= TCP_ERR_CONNECT )
            {
                std.stdio.writeln("DisconnectToServer");
                DisconnectToServer();
            }
        }
    }
    
    private void ProcessRecvPacket(PTcpPacket packet)
    {
        _c_buffer.PutData(cast(char[])packet.data(), packet.size());

        /+int len = _c_buffer.GetValidCount();
        if (len == 0 || len < 0) return false;
        if (len > _buffer.sizeof) return false;
        _c_buffer.GetData(_buffer[], len);

        int increase = 0;
        int first, end;

        if (_buffer[0] == '*')
        {
            first = 1;
            MySocket.sendStar();
        }
        else
            first = 0;

        while (true)
        {
            end = countUntil(_buffer[first .. len], '!');
            if (end == -1)
                break;
            
            auto slice = _buffer[first + 1 .. (first + 1) + (end - 1)];
            //OnSocketMessageRecieve(slice);
            
            end++;
            increase += end;
            
            if (_buffer[end] == '*')
            {
                MySocket.sendStar();
                end++;
            }
            
            if (_buffer[end] == '#')
                first = end;
            else
            {
                first = 0;
                break;
            }
        }

        if (increase > 0)
            _c_buffer.HeadIncrease(increase);+/
        
        int len = 0;
        char[] data = null;
        
        while (pullOutCore(data, len))
        {
            if (data !is null)
            {
                if (_parse_packet !is null)
                {
                    //Parsing(len, data); //      ½ÇÁ¦ ÆÄ½Ì ÇÔ¼ö...
                    _parse_packet(len, data);
                }

                //data = null;
            }
        }
    }
    
    private bool pullOutCore(ref char[] data, ref int length)
    {
        const PACKET_START = '#';
        const PACKET_END = '!';
        bool foundCore = false;
        
        length = 0;
        
        int len = _c_buffer.GetValidCount();
        
        if (len == 0 || len < 0) return false;
        if (len > _buffer.sizeof) return false;
        
        _c_buffer.GetData(_buffer[], len);
        
        
        /*int end = countUntil(_buffer[0 .. len], '!');
        if (end >= 0)
            foundCore = true;

        data = _buffer[0 .. end];
        length = end;*/
        
        int start = 0, end = 0;
        
        if (_buffer[0] == PACKET_START)
        {
            start = 0;
            for (int i = 0; i < len && !foundCore; i++)
            {
                if (_buffer[i] == PACKET_END)
                {
                    end = i;
                    length = ((end - 1) - (start + 1)) + 1;
                    if ( length < 0 ) break;
                    if ( length > len ) break;
                    
                    // Assign a slice of _buffer to data (don't want to allocate memory)
                    data = _buffer[start + 1 .. (start + 1) + length];
                    //data[length] = 0;
                    foundCore = true;
                    break;
                }
            }
        }
        
        if (foundCore) _c_buffer.HeadIncrease(2 + length);
        
        return foundCore;
    }

    public void sendStar()
    {
        static immutable char[] fmt_data = [ '*' ];

        _handler.Send(cast(void[])fmt_data, fmt_data.length);
    }

    public void SendPacket(in DefaultMessage defMsg, char[] data)
    {
        immutable msg_len = EncodeMessage(defMsg, _encode_def_msg);
        char[] fmt_data;
        
        ((_sync >= 9) ? _sync = 1 : _sync++);
        
        if (data.length > 0)
        {
            immutable data_len = EncodeBuffer(&data[0], data.length, _encode_body);
            fmt_data = sformat(_packet, "#%s%s%s!\0", _sync, 
                    _encode_def_msg[0..msg_len], _encode_body[0..data_len]);
        }
        else
            fmt_data = sformat(_packet, "#%s%s!\0", _sync, _encode_def_msg[0..msg_len]);

        std.stdio.writeln("SendPacket: ", data, " ", fmt_data);
        _handler.Send(cast(void[])fmt_data, fmt_data.length);
    }
    
    // For Login
    public void SendLogin(string userid, string passwd)
    {
        char szPacket[64];

        MakeDefMessage(_def_msg, CM_IDPASSWORD, 0, 0, 0, 0);

        auto fmt_data = sformat(szPacket, "%s/%s\0", userid, passwd);

        SendPacket(_def_msg, fmt_data);
    }

    public void SendSelectServer(string servername)
    {
        MakeDefMessage(_def_msg, CM_SELECTSERVER, 0, 0, 0, 0);
        SendPacket(_def_msg, servername.dup);
    }

    public void SendChangePassword(string userid, string oldpasswd, string newpasswd)
    {
        const char divider = 0x09;
        char szPacket[64];

        MakeDefMessage(_def_msg, CM_CHANGEPASSWORD, 0, 0, 0, 0);

        auto fmt_data = sformat(szPacket, "%s%s%s%s%s\0", userid, divider, oldpasswd, divider, newpasswd);
        
        SendPacket(_def_msg, fmt_data);
    }

    public void SendNewAccount(void *pszNewAccount)
    {
        // TODO:
    }
    
    // For Character Select
    public void SendQueryChar(string userid, int cert)
    {
        char szPacket[32];
        
        MakeDefMessage(_def_msg, CM_QUERYCHR, 0, 0, 0, 0);

        auto fmt_data = sformat(szPacket, "%s/%s\0", userid, cert);
        
        SendPacket(_def_msg, fmt_data);
    }

    public void SendNewChar(string userid, int hair, int job, int gender)
    {
        char szPacket[64];
        
        MakeDefMessage(_def_msg, CM_NEWCHR, 0, 0, 0, 0);

        auto fmt_data = sformat(szPacket, "%s/%s/%s/%s\0", userid, hair, job, gender);
        
        SendPacket(_def_msg, fmt_data);
    }

    public void SendDelChar(string username)
    {
        MakeDefMessage(_def_msg, CM_DELCHR, 0, 0, 0, 0);
        SendPacket(_def_msg, username.dup);
    }

    public void SendSelChar(string userid, string username)
    {
        char szPacket[32];
        
        MakeDefMessage(_def_msg, CM_SELCHR, 0, 0, 0, 0);

        auto fmt_data = sformat(szPacket, "%s/%s\0", userid, username);
        
        SendPacket(_def_msg, fmt_data);
    }
    
    // For Game playing
    public void SendNoticeOK()
    {
        MakeDefMessage(_def_msg, CM_LOGINNOTICEOK, 0, 0, 0, 0);
        SendPacket(_def_msg, null);
    }

    public void SendNPCClick(int npc)
    {
        MakeDefMessage(_def_msg, CM_CLICKNPC, npc, 0, 0, 0);
        SendPacket(_def_msg, null);
    }
    
    public void SendNPCMessage(ushort ident, int npc, int reg, string message)
    {
        MakeDefMessage(_def_msg, ident, npc, 0, 0, cast(ushort)reg);
        SendPacket(_def_msg, message.dup);
    }

    public void SendGetDetailItem(int npc, int serial, string iname)
    {
        MakeDefMessage(_def_msg, CM_USERGETDETAILITEM, npc, cast(ushort)serial, 0, 0);
        SendPacket(_def_msg, iname.dup);
    }

    public void SendBuyItem(int npc, string iname, int serial)
    {
        MakeDefMessage(_def_msg, CM_USERBUYITEM, npc, 0, 0, 0);
        SendPacket(_def_msg, iname.dup);
    }
    
    public void SendOpenDoor(int x, int y, int idx)
    {
        MakeDefMessage(_def_msg, CM_OPENDOOR, idx, cast(ushort)x, cast(ushort)y, 0);
        SendPacket(_def_msg, null);
    }

    public void SendQueryMsg(ushort ident)
    {
        MakeDefMessage(_def_msg, ident, 0, 0, 0, 0);
        SendPacket(_def_msg, null);
    }

    public void SendActMsg(ushort ident, int x, int y, int dir)
    {
        MakeDefMessage(_def_msg, ident, MakeLong(cast(ushort)x, cast(ushort)y), 0, cast(ushort)dir, 0);
        SendPacket(_def_msg, null);
    }

    public void SendHitMsg(ushort ident, int x, int y, int dir, ushort hitStyle)
    {
        MakeDefMessage(_def_msg, ident, MakeLong(cast(ushort)x, cast(ushort)y), 0, cast(ushort)dir, 0);
        SendPacket(_def_msg, null);
    }

    public void SendRideMsg(ushort ident, int dir, bool ride)
    {
        MakeDefMessage(_def_msg, ident, 0, cast(ushort)dir, ride, 0);
        SendPacket(_def_msg, null);
    }

    public void SendSpellMsg(int magic, int tx, int ty, int target)
    {
        MakeDefMessage(_def_msg, CM_SPELL, MakeLong(cast(ushort)tx, cast(ushort)ty), LoWord(target), cast(ushort)magic, HiWord(target));
        SendPacket(_def_msg, null);
    }

    public void SendSay(string message)
    {
        MakeDefMessage(_def_msg, CM_SAY, 0, 0, 0, 0);
        SendPacket(_def_msg, message.dup);
    }

    public void SendRunLogin(string userid, string username, int cert, int version_)
    {
        ((_sync >= 9) ? _sync = 1 : _sync++);
        
        auto fmt_data = sformat(_packet, "#%s**%s/%s/%s/%s/1!\0", _sync, userid, username, cert, version_);

        _handler.Send(cast(void[])fmt_data, fmt_data.length);
    }

    public void SendTakeOnOffItem(ushort ident, ubyte where, string iname, int serial)
    {
        MakeDefMessage(_def_msg, ident, cast(ushort)serial, where, 0, 0);
        SendPacket(_def_msg, iname.dup);
    }

    public void SendQueryName(int target, int x, int y)
    {
        MakeDefMessage(_def_msg, CM_QUERYUSERNAME, target, cast(ushort)x, cast(ushort)y, 0);
        SendPacket(_def_msg, null);
    }

    public void SendMagicKeyChange(int key, int magic)
    {
        MakeDefMessage(_def_msg, CM_MAGICKEYCHANGE, magic, cast(ushort)key, 0, 0);
        SendPacket(_def_msg, null);
    }

    public void SendItemIndex(ushort ident, int serial, string iname)
    {
        MakeDefMessage(_def_msg, ident, serial, 0, 0, 0);
        SendPacket(_def_msg, iname.dup);
    }

    public void SendPickUp(int x, int y)
    {
        MakeDefMessage(_def_msg, CM_PICKUP, 0, cast(ushort)x, cast(ushort)y, 0);
        SendPacket(_def_msg, null);
    }

    public void SendButchAnimal(int x, int y, int dir, int target)
    {
        MakeDefMessage(_def_msg, CM_BUTCH, target, cast(ushort)x, cast(ushort)y, cast(ushort)dir);
        SendPacket(_def_msg, null);
    }

    public void SendGroupMessage(ushort ident, string message)
    {
        MakeDefMessage(_def_msg, ident, 0, 0, 0, 0);
        SendPacket(_def_msg, message.dup);
    }

    public void SendGroupMode(bool flag)
    {
        // 1: On, 0: Off
        ushort onoff = (flag) ? 1 : 0;

        MakeDefMessage(_def_msg, CM_GROUPMODE, 0, onoff, 0, 0);
        
        SendPacket(_def_msg, null);
    }

    // PDS:
    /*public void SendCreateGroup(string username)
    {
        MakeDefMessage(_def_msg, CM_CREATEGROUP, 0, 0, 0, 0);
        SendPacket(_def_msg, username.dup);
    }

    public void SendGroupMode(bool flag)
    {
        if (flag)
            MakeDefMessage(_def_msg, CM_GROUPMODE, 0, 1, 0, 0); // On
        else
            MakeDefMessage(_def_msg, CM_GROUPMODE, 0, 0, 0, 0); // Off
        
        SendPacket(_def_msg, null);
    }

    public void SendAddGroupMember(string username)
    {
        MakeDefMessage(_def_msg, CM_ADDGROUPMEMBER, 0, 0, 0, 0);
        SendPacket(_def_msg, username.dup);
    }

    public void SendDelGroupMember(string username)
    {
        MakeDefMessage(_def_msg, CM_DELGROUPMEMBER, 0, 0, 0, 0);
        SendPacket(_def_msg, username.dup);
    }*/
    
    public void SendGuildHome()
    {
        MakeDefMessage(_def_msg, CM_GUILDHOME, 0, 0, 0, 0);
        SendPacket(_def_msg, null);
    }
}